import { _decorator, Vec3, v3, Rect, v2, Size, Intersection2D, rect } from 'cc';
import { ColliderGroup, GroupConfig, CollisionManager } from './CollisionManager';
const TMPP0 = v3();
const TMPP2 = v2();
/**碰撞基类 
 * 1.标记在碰撞地图中的数据
 * 2.检测自身在地图中可能碰撞到的物体
 * 3.每个对象绑定此碰撞体在回收之后 必须重建此对象 ！！
 */
export class BasicCollider<T> {
    //用于创建id
    public static _COLLODERINDEX = 1;
    //唯一碰撞体id
    public SelfId = 0;
    //分组id
    public GroupId = ColliderGroup.None;
    //是否是触发器-不阻碍移动
    public isTrigger = false;

    //记录前一帧的格子id 和 记录的id下标
    public prePosInfo: { p: Vec3, index: number }[] = [];
    //记录当前矩形分割后的坐标
    public curRectPosArr: Vec3[] = [];
    //自身尺寸  和格子的比例一致
    public selfRect: Rect = new Rect(0, 0, 1, 1);
    //判断位置是否更新
    public isDirty = false;

    //地图范围信息记录
    public mapRange = { maxX: 0, minX: 0, maxY: 0, minY: 0, maxZ: 0, minZ: 0, x: 0, y: 0, z: 0 };

    //当前碰撞体对应的节点 
    public targetCmp: T = null;

    //是否作为圆形碰撞检测 -- 限制移动检测时 使用圆形检测
    public isCircleCollider = false;

    constructor(groupId: ColliderGroup, targetCmp: T, isTrigger = false) {
        this.GroupId = groupId;
        this.targetCmp = targetCmp;
        this.isTrigger = isTrigger;
        this.init();
    }

    //#region -----------设置、更新信息----------
    /**设置碰撞矩形位置 */
    public setRectPos(p: Vec3) {
        if (p.x != this.selfRect.center.x ||
            p.z != this.selfRect.center.y) {
            this.selfRect.center = TMPP2.set(p.x, p.z);
            this.updatePosRecFromRect(this.selfRect, this.curRectPosArr);
            this.isDirty = true;
        }
    }
    /**设置单个点的位置(不使用矩形分割) */
    public setSinglePoint(p) {
        if (p.x != this.selfRect.center.x ||
            p.z != this.selfRect.center.y) {
            this.selfRect.center = TMPP2.set(p.x, p.z);
            this.updatePosRecFromPos(p);
            this.isDirty = true;
        }
    }

    /**设置碰撞矩形大小  */
    public setRectSize(s: Size) {
        if (s.width != this.selfRect.width ||
            s.height != this.selfRect.height) {
            this.selfRect.set(this.selfRect.x, this.selfRect.y, s.width, s.height);
            this.updatePosRecFromRect(this.selfRect, this.curRectPosArr);
            this.isDirty = true;
        }
    }
    /**清除在地图中的数据 */
    public clearGridInfo() {
        for (let j = 0; j < this.prePosInfo.length; j++) {
            CollisionManager.clearInfo(this.prePosInfo[j].p, this.GroupId, this.SelfId, this.prePosInfo[j].index);
        }
    }
    /**将此碰撞体从管理器中移除 */
    public removeFromManager() {
        CollisionManager.removeColliderByColId(this.SelfId);
    }

    //#endregion

    //#region --------------流程-----------
    public init(): void {
        //唯一id
        this.SelfId = BasicCollider._COLLODERINDEX++;
        //将碰撞rect 记录到 CollisionManager
        CollisionManager.setColilderByColId(this.SelfId, this);

        //地图范围信息 xz平面 原点左上角
        let wp = CollisionManager.mapWpos;
        let s = CollisionManager.mapSize;
        this.mapRange = {
            maxX: s.x + wp.x,
            minX: wp.x,
            maxY: s.y + wp.y,
            minY: wp.y,
            maxZ: s.z + wp.z,
            minZ: wp.z,
            x: wp.x, y: wp.y, z: wp.z
        }

    }
    public tmpP2 = v3();
    public stepP = v3();
    /**更新自身在地图的位置信息*/
    public updateGridInfo(isForth = false) {
        //
        if (!this.isDirty && !isForth) return;
        //清除之前位置下的信息
        this.clearGridInfo();
        //记录当前位置id
        for (let i = 0; i < this.curRectPosArr.length; i++) {
            if (!this.prePosInfo[i]) {
                this.prePosInfo[i] = { p: v3(), index: -1 };
            }
            this.prePosInfo[i].index = CollisionManager.setInfo(this.curRectPosArr[i], this.GroupId, this.SelfId, this.prePosInfo[i].p);
        }
        this.isDirty = false;
    }

    public lateUpdate(dt) {
        /**在update之后执行 只有rect不断变化时需要更新*/
        this.updateGridInfo();
    }
    //#endregion

    // #region ------------粗略检测----------

    /**
     * 根据自身的矩形包围盒(selfRect)来计算碰撞粗略信息--推荐方法
     * @param isCheckGroup 是否根据分组配置检测
     * @param out //返回信息
     */
    public impreciseCheckByRect(out: { [groupId: number]: number[] }, isCheckGroup = true) {
        let isCollision = false;
        for (const key in this.curRectPosArr) {
            const p = this.curRectPosArr[key];
            let isColl = this.impreciseCheckByPos(p, out, isCheckGroup);
            if (isColl) {
                isCollision = true;
            }
        }
        return isCollision;
    }

    /**
    * 返回初步检测的碰撞信息-不精确
    * @param pos 位置 
    * @param outRes 返回的信息
    * @param isCheckGroup 是否根据分组配置检测
    * @returns 
    */
    public impreciseCheckByPos(pos: Vec3, outRes: { [groupId: number]: number[] }, isCheckGroup = true) {
        let isColl = false;
        const res = CollisionManager.getInfo(pos);
        if (!res) {
            console.warn('# 获取检测数据错误 pos:' + pos);
            return false;
        }
        //根据碰撞分组信息检测
        for (const key in res) {
            const arr = res[key];
            const groupId = +key;

            //是否根据分组来检测 是否能发生碰撞
            if (arr.length > 0 &&
                ((isCheckGroup && GroupConfig[this.GroupId].indexOf(groupId) >= 0) || !isCheckGroup)) {
                if (!outRes[groupId]) {
                    outRes[groupId] = [];
                }
                //记录碰撞的id (id>=0)
                for (let j = 0; j < arr.length; j++) {
                    const colId = arr[j];
                    //过滤重复id与自身id
                    if (colId >= 0 && colId != this.SelfId && outRes[groupId].indexOf(colId) < 0) {
                        outRes[groupId].push(colId);
                    }
                }
                if (!isColl) {
                    isColl = outRes[groupId].length > 0;
                }
            }

        }
        return isColl;
    }

    /**
     * 根据Rect包围盒分割成多个计算点 xz平面
     */
    public updatePosRecFromRect(rect: Rect, outArr: Vec3[]) {
        const gs = CollisionManager.gridSize;
        const _x = Math.ceil(rect.width / gs.x);
        const _z = Math.ceil(rect.height / gs.z);
        let i = 0;
        for (let x = 0; x <= _x; x++) {
            for (let z = 0; z <= _z; z++) {
                if (!outArr[i]) {
                    outArr[i] = v3();
                }
                outArr[i].set(x * gs.x, 0, z * gs.z);
                outArr[i].add3f(rect.x, 0, rect.y)
                i++;
            }
        }
    }
    //xz平面 只计算一个点的情况 
    public updatePosRecFromPos(p: Vec3) {
        if (!this.curRectPosArr[0]) {
            this.curRectPosArr[0] = v3();
        }
        this.curRectPosArr[0].x = Math.ceil(p.x);
        this.curRectPosArr[0].y = 0;
        this.curRectPosArr[0].z = Math.ceil(p.z);
    }
    //#endregion

    //#region ----------对外提供的工具方法-------
    //获取指定矩形内的所有碰撞体
    private _tmpArr: Vec3[] = [];
    public getCollInfoByRect(rect: Rect, groupArr: number[], outRes: { [groupId: number]: number[] }) {
        this._tmpArr = [];
        this.updatePosRecFromRect(rect, this._tmpArr);

        let isCollision = false;
        for (const key in this._tmpArr) {
            const p = this._tmpArr[key];
            let isColl = this.impreciseCheckByPosTool(p, outRes, groupArr);
            if (isColl) {
                isCollision = true;
            }
        }

        return isCollision;
    }
    /**根据坐标粗略检测范围内的指定 分组 物体 */
    public impreciseCheckByPosTool(pos: Vec3, outRes: { [groupId: number]: number[] }, groupArr: number[]) {
        let isColl = false;
        const res = CollisionManager.getInfo(pos);
        if (!res) {
            console.warn('# 获取检测数据错误 pos:' + pos);
            return false;
        }
        //根据碰撞分组信息检测
        for (const key in res) {
            const arr = res[key];
            const groupId = +key;

            //是否根据分组来检测 是否能发生碰撞
            if (arr.length > 0 && groupArr.indexOf(groupId) >= 0) {
                if (!outRes[groupId]) {
                    outRes[groupId] = [];
                }
                //记录碰撞的id (id>=0)
                for (let j = 0; j < arr.length; j++) {
                    const colId = arr[j];
                    //过滤重复id与自身id
                    if (colId >= 0 && outRes[groupId].indexOf(colId) < 0) {
                        outRes[groupId].push(colId);
                    }
                }
                if (!isColl) {
                    isColl = outRes[groupId].length > 0;
                }
            }
        }
        return isColl;
    }

    private _tmpRect = rect();
    /**
   * 将自身的rect 与给定的colIdArr 做精细碰撞检测 每次检测碰撞后回调一次
   * @param colIdArr 
   * @param out 碰撞到的矩形
   */
    public checkRectByColIdArrCb(colIdArr: number[], cb: (out: { colId: number, rect: Rect, fixPos?: Vec3 }) => void, isCheckTrigger = true) {
        if (!colIdArr) return false;
        let out = { colId: -1, rect: this._tmpRect };
        for (let i = 0; i < colIdArr.length; i++) {
            if (this.checkRectByColId(colIdArr[i], out, isCheckTrigger)) {
                cb && cb(out);
            }
        }
        return false;
    }

    /**
     * 将自身的rect 与给定的colIdArr 做精细碰撞检测 检测到一次后就返回 
     * @param colIdArr 
     * @param out 碰撞到的矩形
     */
    public checkRectByColIdArr(colIdArr: number[], out?: { colId: number, rect: Rect, fixPos?: Vec3 }, isCheckTrigger = true) {
        if (!colIdArr) return false;

        for (let i = 0; i < colIdArr.length; i++) {
            if (this.checkRectByColId(colIdArr[i], out, isCheckTrigger)) {
                return true;
            }
        }
        return false;
    }
    private _tmpDirec = v2();
    /**
    * 将自身的rect 与给定的colId 做精细碰撞检测
    * @param colId 
    */
    public checkRectByColId(colId: number, out?: { colId: number, rect: Rect, fixPos?: Vec3 }, isCheckTrigger = true) {
        let isColl = false;
        const otherCollider = CollisionManager.getColilderByColId(colId);
        if (otherCollider) {
            if (isCheckTrigger || !isCheckTrigger && !otherCollider.isTrigger) {
                this._tmpRect.set(otherCollider.selfRect);
                const scp = this.selfRect.center;
                const ocp = otherCollider.selfRect.center;
                const scw = this.selfRect.width * 0.5;
                const ocw = otherCollider.selfRect.height * 0.5;
                //1.双方都是圆形
                if (this.isCircleCollider && otherCollider.isCircleCollider) {
                    //使用圆形检测
                    isColl = Intersection2D.circleCircle(scp, scw, ocp, ocw);
                    //位置修正
                    if (isColl && out && out.fixPos) {
                        this._tmpDirec.set(scp).subtract(ocp);
                        this._tmpDirec.normalize().multiplyScalar(scw + ocw).add(ocp);
                        out.fixPos.set(this._tmpDirec.x, 0, this._tmpDirec.y);
                    }

                }

                //2.双方都使用矩形检测
                if (!this.isCircleCollider && !otherCollider.isCircleCollider) {
                    isColl = Intersection2D.rectRect(this.selfRect, this._tmpRect);
                }

                //3.自身圆形 对方矩形
                if (this.isCircleCollider && !otherCollider.isCircleCollider) {
                    isColl = Intersection2D.rectCircle(otherCollider.selfRect, scp, scw);
                }

                //4.自身矩形 对方圆形
                if (!this.isCircleCollider && otherCollider.isCircleCollider) {
                    isColl = Intersection2D.rectCircle(this.selfRect, ocp, ocw);
                }

                if (isColl && out) {
                    out.colId = colId;
                    out.rect.set(this._tmpRect);
                }
            }
        }
        return isColl;
    }


    //#endregion

    //#region ----------碰撞回调--------
    /**
     * 对方主动检测时 调用此方法,自身碰撞不需调用
     */
    public onOtherCollision(other: BasicCollider<T>, self: BasicCollider<T>) {

    }
    //#region 

    /**
     * 根据Rect包围盒分割成多个计算点 xy平面
     */
    // public updatePosRecFromRect(rect: Rect, out: Vec3[]) {
    //     const gs = CollisionManager.gridSize;
    //     const _x = Math.ceil(rect.width / gs.x);
    //     const _y = Math.ceil(rect.height / gs.y);
    //     let i = 0;
    //     for (let x = 0; x <= _x; x++) {
    //         for (let y = 0; y <= _y; y++) {
    //             if (!out[i]) {
    //                 out[i] = v3();
    //             }
    //             out[i].set(x * gs.x, y * gs.y);
    //             out[i].add3f(rect.x, rect.y, 0)
    //             i++;
    //         }
    //     }
    // }
}

